package com.gitee.wsl.doc.prosemirror.transform.step

import com.gitee.wsl.doc.prosemirror.exception.RangeError
import com.gitee.wsl.doc.prosemirror.model.node.Node
import com.gitee.wsl.doc.prosemirror.model.Schema
import com.gitee.wsl.doc.prosemirror.model.fragment.Slice
import com.gitee.wsl.doc.prosemirror.model.node.ext.contentBetween
import com.gitee.wsl.doc.prosemirror.transform.map.Mappable
import com.gitee.wsl.doc.prosemirror.transform.map.StepMap
import kotlin.math.max
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.put

// Replace a part of the document with a slice of new content.
data class ReplaceStep(
    // The given `slice` should fit the 'gap' between `from` and `to`—the depths must line up, and
    // the surrounding nodes must be able to be joined with the open sides of the slice. When
    // `structure` is true, the step will fail if the content between from and to is not just a
    // sequence of closing and then opening tokens (this is to guard against rebased replace steps
    // overwriting something they weren't supposed to).

    // The start position of the replaced range.
    override val from: Int,
    // The end position of the replaced range.
    override val to: Int,
    // The slice to insert.
    val slice: Slice,
    internal val structure: Boolean = false
) : Step() {
    override fun apply(doc: Node): StepResult {
        if (this.structure && doc.contentBetween( this.from, this.to)) {
            return StepResult.fail("Structure replace would overwrite content")
        }
        return StepResult.fromReplace(doc, this.from, this.to, this.slice)
    }

    override fun getMap() = StepMap(listOf(this.from, this.to - this.from, this.slice.size))

    override fun invert(doc: Node): ReplaceStep {
        return ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
    }

    override fun map(mapping: Mappable): ReplaceStep? {
        val from = mapping.mapResult(this.from, 1)
        val to = mapping.mapResult(this.to, -1)
        if (from.deletedAcross && to.deletedAcross) return null
        return ReplaceStep(from.pos, max(from.pos, to.pos), this.slice)
    }

    @Suppress("ReturnCount")
    override fun merge(other: Step): ReplaceStep? {
        if (other !is ReplaceStep || other.structure || this.structure) return null

        if (this.from + this.slice.size == other.from && this.slice.openEnd == 0 && other.slice.openStart == 0) {
            val slice = if (this.slice.size + other.slice.size == 0) {
                Slice.empty
            } else {
                Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd)
            }
            return ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure)
        } else if (other.to == this.from && this.slice.openStart == 0 && other.slice.openEnd == 0) {
            val slice = if (this.slice.size + other.slice.size == 0) {
                Slice.empty
            } else {
                Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd)
            }
            return ReplaceStep(other.from, this.to, slice, this.structure)
        } else {
            return null
        }
    }

    override fun toJSON(): JsonObject {
        return buildJsonObject {
            put("stepType", "replace")
            put("from", from)
            put("to", to)
            if (slice.size != 0) slice.toJSON()?.let { json -> put("slice", json) }
            if (structure) put("structure", true)
        }
    }

    companion object : StepJsonParser<ReplaceStep> {
        init {
            jsonID("replace", this)
        }

        override fun fromJSON(schema: Schema, json: JsonObject): ReplaceStep {
            val from = json["from"]?.jsonPrimitive?.int
            val to = json["to"]?.jsonPrimitive?.int
            if (from == null || to == null) {
                throw RangeError("Invalid input for ReplaceStep.fromJSON")
            }
            val slice = json["slice"]?.jsonObject
            val structure = json["structure"]?.jsonPrimitive?.booleanOrNull ?: false
            return ReplaceStep(from, to, Slice.fromJSON(schema, slice), structure)
        }
    }
}

fun Step.Companion.replace(
     // The start position of the replaced range.
     from: Int,
     // The end position of the replaced range.
     to: Int,
     // The slice to insert.
     slice: Slice) = ReplaceStep( from, to, slice)
